Zohaib Zahid
Project #2 – Data Exploration and Design
Due: 2/22/2021
Data
The dataset chosen for this assignment is from Kaggle: https://www.kaggle.com/kenhuang41/nba-basic-game-data-by-player
This dataset features individual player data from all games they played in from start of 1996-97 season to December 31, 2020.
Loading in the library
library(dplyr)
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(ggplot2)
Read the file
games <- read.csv(file = 'games.csv')
Check the top of the data:
head(games)
- GAMES_ID - Unique Game ID (Nominal)
- TEAM - Player’s team (Nominal)
- OPPT - Opponent’s team (Nominal)
- TEAM_SCORE - Player’s Team Score (Interval)
- OPPY_SCORE - Oppoenet’s Team Score (Interval)
- RESULT - Win/Loss (Ordinal)
- SCORE_DIFF - Team Score - Opponent Team (Interval)
- PLAYER - Player Name (Nominal)
- MP - Minutes played (Interval)
- FG - Field Goals Made (Interval)
- FGA - Field Goals Attempted (Interval)
- FG3 - 3 Pointers Made (Interval)
- FG3A - 3 Pointers Attempted (Interval)
- FT - Free Throws Made (Interval)
- FTA - Free Throws Attempted (Interval)
- ORB - Offensive Rebounds (Interval)
- DRB - Defensive Rebounds (Interval)
- TRB - Total Rebounds (Interval)
- AST - Assists (Interval)
- STL - Steals (Interval)
- BLK - Blocks (Interval)
- TOV - Turnovers (Interval)
- PF - Personal Fouls (Interval)
- PLUS_MINUS - Individual Plus Minus (Interval)
- PTS - Total Points (Interval)
- TOTAL_MINS - Total Minutes (Interval)
- STARTER - Started or Not (Ordina)
Run a quick analysis on the data provided:
summary(games)
GAME_ID TEAM OPPT TEAM_SCORE OPPT_SCORE RESULT
Length:743423 Length:743423 Length:743423 Min. : 49.00 Min. : 49.0 Length:743423
Class :character Class :character Class :character 1st Qu.: 91.00 1st Qu.: 91.0 Class :character
Mode :character Mode :character Mode :character Median : 99.00 Median : 99.0 Mode :character
Mean : 99.51 Mean : 99.5
3rd Qu.:108.00 3rd Qu.:108.0
Max. :168.00 Max. :168.0
SCORE_DIFF PLAYER MP FG FGA FG3 FG3A
Min. :-65.00000 Length:743423 Min. : 0.00 Min. : 0.000 Min. : 0.000 Min. : 0.000 Min. : 0.000
1st Qu.: -9.00000 Class :character 1st Qu.: 7.00 1st Qu.: 0.000 1st Qu.: 1.000 1st Qu.: 0.000 1st Qu.: 0.000
Median : 1.00000 Mode :character Median :21.00 Median : 2.000 Median : 6.000 Median : 0.000 Median : 0.000
Mean : 0.00897 Mean :19.73 Mean : 3.022 Mean : 6.688 Mean : 0.566 Mean : 1.593
3rd Qu.: 9.00000 3rd Qu.:31.00 3rd Qu.: 5.000 3rd Qu.:10.000 3rd Qu.: 1.000 3rd Qu.: 3.000
Max. : 65.00000 Max. :65.00 Max. :28.000 Max. :50.000 Max. :14.000 Max. :24.000
FT FTA ORB DRB TRB AST STL
Min. : 0.000 Min. : 0.000 Min. : 0.0000 Min. : 0.000 Min. : 0.000 Min. : 0.000 Min. : 0.0000
1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.0000 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.0000
Median : 0.000 Median : 0.000 Median : 0.0000 Median : 2.000 Median : 3.000 Median : 1.000 Median : 0.0000
Mean : 1.498 Mean : 1.985 Mean : 0.9212 Mean : 2.535 Mean : 3.457 Mean : 1.781 Mean : 0.6242
3rd Qu.: 2.000 3rd Qu.: 3.000 3rd Qu.: 1.0000 3rd Qu.: 4.000 3rd Qu.: 5.000 3rd Qu.: 3.000 3rd Qu.: 1.0000
Max. :26.000 Max. :39.000 Max. :18.0000 Max. :25.000 Max. :33.000 Max. :25.000 Max. :11.0000
BLK TOV PF PLUS_MINUS PTS TOTAL_MINS STARTER
Min. : 0.0000 Min. : 0.000 Min. :0.00 Min. :-5.7e+01 Min. : 0.000 Min. :48.00 Length:743423
1st Qu.: 0.0000 1st Qu.: 0.000 1st Qu.:0.00 1st Qu.:-5.0e+00 1st Qu.: 0.000 1st Qu.:48.00 Class :character
Median : 0.0000 Median : 1.000 Median :2.00 Median : 0.0e+00 Median : 6.000 Median :48.00 Mode :character
Mean : 0.4012 Mean : 1.137 Mean :1.74 Mean :-3.0e-05 Mean : 8.109 Mean :48.36
3rd Qu.: 1.0000 3rd Qu.: 2.000 3rd Qu.:3.00 3rd Qu.: 5.0e+00 3rd Qu.:13.000 3rd Qu.:48.00
Max. :13.0000 Max. :14.000 Max. :7.00 Max. : 5.7e+01 Max. :81.000 Max. :68.00
Notice that this dataset has 743423 entries. This represents every player, in every game played since 1996-97 season, upto December 31, 2020. When taking a closer look at the score difference, it is pretty symmetrical almost with the mean being zero, because the data also include data of those players on the losing team, therefore the score difference shown for that player is negative.
For some players this season is mid career, for others it’s the start. However, for this analysis all that matters is the time period of this dataset.
To make this data a bit more useful to us, lets add a column just for year the game was played, which can be processed as a subset of the game_ID. Although the year does not exactly correspond to the season since seasons go across 2 years usually, this will still help analyze how over the years players have changed.
games <- mutate(games, year = substr(GAME_ID, 12, 15))
Now, Lets take a look at the score distribution of the winning teams in this dataset:
by_game <- group_by(games,GAME_ID)
by_winning_game_score <- group_by(by_game,TEAM_SCORE, .add=TRUE) %>%
filter(RESULT=="W")
Winning_Games_dist <- summarise(by_winning_game_score)
`summarise()` has grouped output by 'GAME_ID'. You can override using the `.groups` argument.
hist(Winning_Games_dist$TEAM_SCORE)
abline(v = mean(Winning_Games_dist$TEAM_SCORE), col = "blue", lwd = 2)

boxplot(Winning_Games_dist$TEAM_SCORE)

summary(Winning_Games_dist$TEAM_SCORE)
Min. 1st Qu. Median Mean 3rd Qu. Max.
64.0 97.0 104.0 104.9 112.0 168.0
Lets take a look at the score distribution of the losing teams in this dataset:
by_game <- group_by(games,GAME_ID)
by_losing_game_score <- group_by(by_game,TEAM_SCORE, .add=TRUE) %>%
filter(RESULT=="L")
Losing_Games_dist <- summarise(by_losing_game_score)
`summarise()` has grouped output by 'GAME_ID'. You can override using the `.groups` argument.
hist(Losing_Games_dist$TEAM_SCORE)
abline(v = mean(Losing_Games_dist$TEAM_SCORE), col = "blue", lwd = 2)

boxplot(Losing_Games_dist$TEAM_SCORE)

summary(Losing_Games_dist$TEAM_SCORE)
Min. 1st Qu. Median Mean 3rd Qu. Max.
49.00 86.00 93.00 93.94 101.00 161.00
Analysis
Now that there is a little better understanding of the data, lets take a look at five analytical questions at hand:
- Players with the most points scored since 1996-97 season?
- Players with the most games over 40 points?
- Who has the best field goal ratios in their career?*
- Player with the best overall stats in their career?*
- Which team did LeBron play the best on?*
- the questions selected for the visualization
Question 1 - Players with the most points scored since 1996-97 season?
by_player <- group_by(games,PLAYER)
most_points_scored <- summarise(by_player, points = sum(PTS)) %>%
arrange(desc(points)) %>%
top_n(5)
Selecting by points
most_points_scored
Here is a list of the top 5 players with the highest points scored since 1996-97 season.
Question 2 - Players with the most games over 40 points?
most_games_over_40pts <- group_by(games,PLAYER) %>%
filter(PTS>40) %>%
count(PLAYER) %>%
arrange(desc(n)) %>%
ungroup(PLAYER) %>%
top_n(5)
Selecting by n
most_games_over_40pts
NA
Here is a list of the top 5 players with the most games with atleast 40 points since 1996-97 season.
Now that we have a few of what I would consider the top players in the NBA, according to total points scored and most games with atleast 40 pts. Lets analyze a few stats from these individuals to try and help narrow down and determine who is the best of these player in the NBA since 1996-97 season.
Question 3 - Who has the best field goal ratios in their career?
fgr <- mutate(games, FGR = FG/FGA, FGR3PT = FG3/FG3A, FGRFT = FT/FTA) %>%
filter(PLAYER == most_games_over_40pts$PLAYER | PLAYER == most_points_scored$PLAYER)
longer object length is not a multiple of shorter object lengthlonger object length is not a multiple of shorter object length
best_fgr <- group_by(fgr,PLAYER) %>%
summarise(avgfgr = mean(FGR, na.rm = TRUE)) %>%
arrange(desc(avgfgr))
best_3ptr <- group_by(fgr,PLAYER) %>%
summarise(avg3ptr = mean(FGR3PT, na.rm = TRUE)) %>%
arrange(desc(avg3ptr))
best_ftr <- group_by(fgr,PLAYER) %>%
summarise(avgftr = mean(FGRFT, na.rm = TRUE)) %>%
arrange(desc(avgftr))
merged_fgr <- merge(best_fgr,best_3ptr,by.x="PLAYER") %>%
merge(best_ftr,by.x="PLAYER")
merged_fgr
NA
See Viz 3.1 below- This visualization is represented by a bar plot, in descending order. The reason for this type of chart for this data was to show the “rank” of these eight (8) players. Looking at both the table and chart will help understand the data better. The other part of this visualization that helps is that it is ranked in decsending order to give the viewer less items to look at all at once, as comparing bar heights can be deceiving sometimes.
See Viz 3.2 below - This visualization is represented by a lollipop chart. The reason for this type of chart for this data was to show the same data from a somewhat different view “rank” of these eight (8) players. However, the data being represented here different and it is using the average 3 point ratio (avg3ptr). Similarly, the chart is in descending order to give the viewer less items to look at all at once.
See Viz 3.3 below - This visualization is represented by a circle plot with 3 axes, one for each column/feature. The reason for this type of chart for this data was to show a comparison between players. For the purpose and proof of concept, used it to represent only 2 players(Lebron James and Kobe Bryant). There would have been 28 combinations for any 2 players, and would best work with an interactive view of some sort to select player 1 and player 2 for comparison. The purpose of this visualization would be to easily be able to compare stats of 2 players versus having to look at multiple charts, and piecing together information.
Extra: In addition, to the career stats, sometimes its meaningful to look at a few per year stats as well. Below is the Field goal ratio per year for these players, along with their points per game per year stats. This will help with making sure the players that are scoring higher points are not doing so at the sacrifice of their field goal percentage, and not have it play into the idea of more shots taken the higher the score.
by_player <- filter(fgr) %>%
group_by(PLAYER, year)
ppg_per_yr <- summarise(by_player, points = mean(PTS, na.rm = TRUE)) %>%
arrange(year)
`summarise()` has grouped output by 'PLAYER'. You can override using the `.groups` argument.
ppg_per_yr
# plot
ggplot(data = ppg_per_yr, aes(x=ppg_per_yr$year, y=ppg_per_yr$points, group = ppg_per_yr$PLAYER, color =ppg_per_yr$PLAYER)) + geom_line() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

As seen in this visualization above, the top 3 players of the 8 that are being examined are James Harden, Lebron James, and Kevin Durant in terms of most points per game per year.
by_player <- filter(fgr) %>%
group_by(PLAYER, year)
best_fgr_per_yr <- summarise(by_player, FGR_per_yr = mean(FGR, na.rm = TRUE)) %>%
arrange(year)
`summarise()` has grouped output by 'PLAYER'. You can override using the `.groups` argument.
best_fgr_per_yr
# plot
ggplot(data = best_fgr_per_yr, aes(x=best_fgr_per_yr$year, y=best_fgr_per_yr$FGR_per_yr, group = best_fgr_per_yr$PLAYER, color =best_fgr_per_yr$PLAYER)) + geom_line() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

Now in the visualizaiton above, lets take a closer look at James Harden, Lebron James, and Kevin Durant in terms of their field goal percentage per year. Notice that James Harden has a lower ratio than both Lebron James and Kevin Durant, leading us to believe he has higher points per game only because of how many shots he took in total.
Question 4 - Player with the best overall stats in their career?
stats <- filter(fgr)
best_stats <- group_by(stats,PLAYER) %>%
summarise(avgTRB = mean(TRB, na.rm = TRUE), avgAST = mean(AST, na.rm = TRUE), avgSTL = mean(STL, na.rm = TRUE), avgBLK = mean(BLK, na.rm = TRUE)) %>%
mutate(total = avgTRB+avgAST+avgSTL+avgBLK) %>%
arrange(desc(total))
best_stats
by_player <- filter(fgr) %>%
group_by(PLAYER, year)
best_stats_per_year <- summarise(by_player, avgTRB = mean(TRB, na.rm = TRUE), avgAST = mean(AST, na.rm = TRUE), avgSTL = mean(STL, na.rm = TRUE), avgBLK = mean(BLK, na.rm = TRUE)) %>%
mutate(total = avgTRB+avgAST+avgSTL+avgBLK) %>%
arrange(desc(total))
`summarise()` has grouped output by 'PLAYER'. You can override using the `.groups` argument.
best_stats_per_year
ggplot(data = best_stats_per_year, aes(x=best_stats_per_year$year, y=best_stats_per_year$total, group = best_stats_per_year$PLAYER, color =best_stats_per_year$PLAYER)) + geom_line() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

See Viz 4.1 above- This visualization is represented by a multiple line plot. This visualization allows the user to view 3 variables at the same time. The visualization is able to show the total stats(y axis), the year(x axis), and the player (colored line).The y axis is calculated by adding together the average total rebounds per game, average assist per game, average steal per game, and average block per game - all for a given year. This is also just a simplification of the stats, as in the real game there are weights associated with these stats, where rebounds and assists are more heavily weighed than blocks and steals.
See Viz 4.2 below- This visualization is represented by a multiple marks. This visualization also allows the user to view 3 variables at the same time. However, in this visualization it shows the separate stats per person and not per year. The average stat score is on the y axis, the stat on the x axis, and each player is represented by a different mark.The y axis represent the average total rebounds per game, average assist per game, average steal per game, and average block per game - in a players whole career.
See Viz 4.3 below- This visualization is represented by stacked bar chart. This visualization also allows the user to view multiple variables at once as well. In this visualization, the stacked bar is able to show the breakdown of the total stat. The total stat score is on the y axis, the player is on the x axis, and each stat is represented by a different shading/texture.These are also representing whoel career stats versus the yearly stats seen in Viz 4_1.
To continue the conversation of who is best, initially in Viz 4_1 it can be seen that Dirk Nowitzki and Tim Duncan were on top, until Lebron James started his career in 2003, and they started on what looks to be a steady decline until retirement. Lebron James since starting in 2003, had 12 out of 18 years of being on the top of this chart, and the other 6 years he was second on this chart. Lebron James has the highest overall stat total from the 8 players seen in Viz 4_3.
Safe to say that statistically, Lebron James is the best overall player in the NBA according to this dataset that covers all games from 1996-97 season to December 31, 2020.
Question 5 - Which team did LeBron play the best on?
by_player <- filter(games, PLAYER=="LeBron James") %>%
group_by(TEAM,year)
Lebron_most_points_per_year <- summarise(by_player, points = sum(PTS)) %>%
arrange(year)
`summarise()` has grouped output by 'TEAM'. You can override using the `.groups` argument.
Lebron_most_points_per_year
# plot
ggplot(data = Lebron_most_points_per_year, aes(x=Lebron_most_points_per_year$TEAM, y=Lebron_most_points_per_year$points, group = Lebron_most_points_per_year$TEAM, color=Lebron_most_points_per_year$TEAM)) + geom_boxplot() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

See Viz 5.1 above- This visualization is represented by a multiple box plots. This visualization allows the spread of scores. The visualization is able to show the total points per year(y axis), the team(x axis). This chart was chosen to not only show the Lebron’s average points scored per year, but to show the range of scores he has scored for a particular team. Notice that the box plot for Lakers is small, that is ultimately due to the limited number of years he has been with that franchise. On average, Lebron had the most points scored per year on the Cleveland Cavaliers.
Lebron_avg_points_per_year <-summarise(by_player, avgMinsPlayed = mean(MP, na.rm=TRUE), points = mean(PTS, na.rm=TRUE)) %>%
arrange(year)
`summarise()` has grouped output by 'TEAM'. You can override using the `.groups` argument.
Lebron_avg_points_per_year
# plot
ggplot(data = Lebron_avg_points_per_year, aes(x=Lebron_avg_points_per_year$year, y=Lebron_avg_points_per_year$points, group = Lebron_avg_points_per_year$TEAM, color=Lebron_avg_points_per_year$TEAM)) + geom_point() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

See Viz 5.2 above- This visualization is represented by a scatter plot. This visualization allows to spread of scores across multiple years. The visualization is able to show the average points per game per year(y axis), the year(x axis). This chart was chosen to to show how Lebron scored on average in any given year. Notice that only his first 2 years in the NBA did he have a 22.5 and lower average game score (which is still very impressive). Also that his top seasons were with the Cleveland Cavaliers.
Lebron_team_summary <- filter(games, PLAYER=="LeBron James") %>%
group_by(TEAM) %>%
count()
Lebron_team_summary
LeBron_Points_Summary <- ungroup(Lebron_most_points_per_year) %>%
group_by(TEAM) %>%
summarize(TotalPts = sum(points))
LeBron_Points_Summary
NA
See Viz 5.3 below- This visualization is represented by pie chart. This visualization also allows the user to view relative share of games played and total points scored with a certain team. Notice here that the games played share and the points scored for Lebron are very highly correlated. which means he has been really consistent during his time with each team.
In conclusion to this question, the team that he performed his best so far is with the Cleveland Cavaliers.
LS0tCnRpdGxlOiAiTkJBIEdhbWUgU3RhdHMgU2luY2UgdGhlIDE5OTYtOTcgU2Vhc29uIgpvdXRwdXQ6CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgd29yZF9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpab2hhaWIgWmFoaWQgIApQcm9qZWN0ICMyIOKAkyBEYXRhIEV4cGxvcmF0aW9uIGFuZCBEZXNpZ24gIApEdWU6IDIvMjIvMjAyMSAgCgo8aDI+RGF0YTwvaDI+CgpUaGUgZGF0YXNldCBjaG9zZW4gZm9yIHRoaXMgYXNzaWdubWVudCBpcyBmcm9tIEthZ2dsZTogCmh0dHBzOi8vd3d3LmthZ2dsZS5jb20va2VuaHVhbmc0MS9uYmEtYmFzaWMtZ2FtZS1kYXRhLWJ5LXBsYXllcgoKVGhpcyBkYXRhc2V0IGZlYXR1cmVzIGluZGl2aWR1YWwgcGxheWVyIGRhdGEgZnJvbSBhbGwgZ2FtZXMgdGhleSBwbGF5ZWQgaW4gZnJvbSBzdGFydCBvZiAxOTk2LTk3IHNlYXNvbiB0byBEZWNlbWJlciAzMSwgMjAyMC4gCgpMb2FkaW5nIGluIHRoZSBsaWJyYXJ5CmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmBgYAoKUmVhZCB0aGUgZmlsZQpgYGB7cn0KZ2FtZXMgPC0gcmVhZC5jc3YoZmlsZSA9ICdnYW1lcy5jc3YnKQpgYGAKCkNoZWNrIHRoZSB0b3Agb2YgdGhlIGRhdGE6CmBgYHtyfQpoZWFkKGdhbWVzKQpgYGAKKiBHQU1FU19JRCAtIFVuaXF1ZSBHYW1lIElEIChOb21pbmFsKSAgCiogVEVBTSAtIFBsYXllcidzIHRlYW0gKE5vbWluYWwpICAKKiBPUFBUIC0gT3Bwb25lbnQncyB0ZWFtIChOb21pbmFsKSAgCiogVEVBTV9TQ09SRSAtIFBsYXllcidzIFRlYW0gU2NvcmUgKEludGVydmFsKSAgCiogT1BQWV9TQ09SRSAtIE9wcG9lbmV0J3MgVGVhbSBTY29yZSAoSW50ZXJ2YWwpICAKKiBSRVNVTFQgLSBXaW4vTG9zcyAoT3JkaW5hbCkgIAoqIFNDT1JFX0RJRkYgLSBUZWFtIFNjb3JlIC0gT3Bwb25lbnQgVGVhbSAoSW50ZXJ2YWwpICAKKiBQTEFZRVIgLSBQbGF5ZXIgTmFtZSAoTm9taW5hbCkgIAoqIE1QIC0gTWludXRlcyBwbGF5ZWQgKEludGVydmFsKSAgCiogRkcgLSBGaWVsZCBHb2FscyBNYWRlIChJbnRlcnZhbCkgIAoqIEZHQSAtIEZpZWxkIEdvYWxzIEF0dGVtcHRlZCAoSW50ZXJ2YWwpICAKKiBGRzMgLSAzIFBvaW50ZXJzIE1hZGUgKEludGVydmFsKSAgCiogRkczQSAtIDMgUG9pbnRlcnMgQXR0ZW1wdGVkIChJbnRlcnZhbCkgIAoqIEZUIC0gRnJlZSBUaHJvd3MgTWFkZSAoSW50ZXJ2YWwpICAKKiBGVEEgLSBGcmVlIFRocm93cyBBdHRlbXB0ZWQgKEludGVydmFsKSAgCiogT1JCIC0gT2ZmZW5zaXZlIFJlYm91bmRzIChJbnRlcnZhbCkgIAoqIERSQiAtIERlZmVuc2l2ZSBSZWJvdW5kcyAoSW50ZXJ2YWwpICAKKiBUUkIgLSBUb3RhbCBSZWJvdW5kcyAoSW50ZXJ2YWwpICAKKiBBU1QgLSBBc3Npc3RzIChJbnRlcnZhbCkgIAoqIFNUTCAtIFN0ZWFscyAoSW50ZXJ2YWwpICAKKiBCTEsgLSBCbG9ja3MgKEludGVydmFsKSAgCiogVE9WIC0gVHVybm92ZXJzIChJbnRlcnZhbCkgIAoqIFBGIC0gUGVyc29uYWwgRm91bHMgKEludGVydmFsKSAgCiogUExVU19NSU5VUyAtIEluZGl2aWR1YWwgUGx1cyBNaW51cyAoSW50ZXJ2YWwpICAKKiBQVFMgLSBUb3RhbCBQb2ludHMgKEludGVydmFsKSAgCiogVE9UQUxfTUlOUyAtIFRvdGFsIE1pbnV0ZXMgKEludGVydmFsKSAgCiogU1RBUlRFUiAtIFN0YXJ0ZWQgb3IgTm90IChPcmRpbmEpICAKClJ1biBhIHF1aWNrIGFuYWx5c2lzIG9uIHRoZSBkYXRhIHByb3ZpZGVkOgpgYGB7cn0Kc3VtbWFyeShnYW1lcykKYGBgCgpOb3RpY2UgdGhhdCB0aGlzIGRhdGFzZXQgaGFzIDc0MzQyMyBlbnRyaWVzLiBUaGlzIHJlcHJlc2VudHMgZXZlcnkgcGxheWVyLCBpbiBldmVyeSBnYW1lIHBsYXllZCBzaW5jZSAxOTk2LTk3IHNlYXNvbiwgdXB0byBEZWNlbWJlciAzMSwgMjAyMC4gV2hlbiB0YWtpbmcgYSBjbG9zZXIgbG9vayBhdCB0aGUgc2NvcmUgZGlmZmVyZW5jZSwgaXQgaXMgcHJldHR5IHN5bW1ldHJpY2FsIGFsbW9zdCB3aXRoIHRoZSBtZWFuIGJlaW5nIHplcm8sIGJlY2F1c2UgdGhlIGRhdGEgYWxzbyBpbmNsdWRlIGRhdGEgb2YgdGhvc2UgcGxheWVycyBvbiB0aGUgbG9zaW5nIHRlYW0sIHRoZXJlZm9yZSB0aGUgc2NvcmUgZGlmZmVyZW5jZSBzaG93biBmb3IgdGhhdCBwbGF5ZXIgaXMgbmVnYXRpdmUuIAoKRm9yIHNvbWUgcGxheWVycyB0aGlzIHNlYXNvbiBpcyBtaWQgY2FyZWVyLCBmb3Igb3RoZXJzIGl0J3MgdGhlIHN0YXJ0LiBIb3dldmVyLCBmb3IgdGhpcyBhbmFseXNpcyBhbGwgdGhhdCBtYXR0ZXJzIGlzIHRoZSB0aW1lIHBlcmlvZCBvZiB0aGlzIGRhdGFzZXQuCgpUbyBtYWtlIHRoaXMgZGF0YSBhIGJpdCBtb3JlIHVzZWZ1bCB0byB1cywgbGV0cyBhZGQgYSBjb2x1bW4ganVzdCBmb3IgeWVhciB0aGUgZ2FtZSB3YXMgcGxheWVkLCB3aGljaCBjYW4gYmUgcHJvY2Vzc2VkIGFzIGEgc3Vic2V0IG9mIHRoZSBnYW1lX0lELiBBbHRob3VnaCB0aGUgeWVhciBkb2VzIG5vdCBleGFjdGx5IGNvcnJlc3BvbmQgdG8gdGhlIHNlYXNvbiBzaW5jZSBzZWFzb25zIGdvIGFjcm9zcyAyIHllYXJzIHVzdWFsbHksIHRoaXMgd2lsbCBzdGlsbCBoZWxwIGFuYWx5emUgaG93IG92ZXIgdGhlIHllYXJzIHBsYXllcnMgaGF2ZSBjaGFuZ2VkLiAKYGBge3J9CmdhbWVzIDwtIG11dGF0ZShnYW1lcywgeWVhciA9IHN1YnN0cihHQU1FX0lELCAxMiwgMTUpKQpgYGAKCk5vdywgTGV0cyB0YWtlIGEgbG9vayBhdCB0aGUgc2NvcmUgZGlzdHJpYnV0aW9uIG9mIHRoZSB3aW5uaW5nIHRlYW1zIGluIHRoaXMgZGF0YXNldDogCmBgYHtyfQpieV9nYW1lIDwtIGdyb3VwX2J5KGdhbWVzLEdBTUVfSUQpCmJ5X3dpbm5pbmdfZ2FtZV9zY29yZSA8LSBncm91cF9ieShieV9nYW1lLFRFQU1fU0NPUkUsIC5hZGQ9VFJVRSkgJT4lCiAgZmlsdGVyKFJFU1VMVD09IlciKQpXaW5uaW5nX0dhbWVzX2Rpc3QgPC0gc3VtbWFyaXNlKGJ5X3dpbm5pbmdfZ2FtZV9zY29yZSkKaGlzdChXaW5uaW5nX0dhbWVzX2Rpc3QkVEVBTV9TQ09SRSkKYWJsaW5lKHYgPSBtZWFuKFdpbm5pbmdfR2FtZXNfZGlzdCRURUFNX1NDT1JFKSwgY29sID0gImJsdWUiLCBsd2QgPSAyKQpib3hwbG90KFdpbm5pbmdfR2FtZXNfZGlzdCRURUFNX1NDT1JFKQpzdW1tYXJ5KFdpbm5pbmdfR2FtZXNfZGlzdCRURUFNX1NDT1JFKQpgYGAKTGV0cyB0YWtlIGEgbG9vayBhdCB0aGUgc2NvcmUgZGlzdHJpYnV0aW9uIG9mIHRoZSBsb3NpbmcgdGVhbXMgaW4gdGhpcyBkYXRhc2V0OiAKYGBge3J9CmJ5X2dhbWUgPC0gZ3JvdXBfYnkoZ2FtZXMsR0FNRV9JRCkKYnlfbG9zaW5nX2dhbWVfc2NvcmUgPC0gZ3JvdXBfYnkoYnlfZ2FtZSxURUFNX1NDT1JFLCAuYWRkPVRSVUUpICU+JQogIGZpbHRlcihSRVNVTFQ9PSJMIikKTG9zaW5nX0dhbWVzX2Rpc3QgPC0gc3VtbWFyaXNlKGJ5X2xvc2luZ19nYW1lX3Njb3JlKQpoaXN0KExvc2luZ19HYW1lc19kaXN0JFRFQU1fU0NPUkUpCmFibGluZSh2ID0gbWVhbihMb3NpbmdfR2FtZXNfZGlzdCRURUFNX1NDT1JFKSwgY29sID0gImJsdWUiLCBsd2QgPSAyKQpib3hwbG90KExvc2luZ19HYW1lc19kaXN0JFRFQU1fU0NPUkUpCnN1bW1hcnkoTG9zaW5nX0dhbWVzX2Rpc3QkVEVBTV9TQ09SRSkKYGBgCgoKPGgyPkFuYWx5c2lzPC9oMj4KCk5vdyB0aGF0IHRoZXJlIGlzIGEgbGl0dGxlIGJldHRlciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkYXRhLCBsZXRzIHRha2UgYSBsb29rIGF0IGZpdmUgYW5hbHl0aWNhbCBxdWVzdGlvbnMgYXQgaGFuZDoKCjEuIFBsYXllcnMgd2l0aCB0aGUgbW9zdCBwb2ludHMgc2NvcmVkIHNpbmNlIDE5OTYtOTcgc2Vhc29uPyAKMi4gUGxheWVycyB3aXRoIHRoZSBtb3N0IGdhbWVzIG92ZXIgNDAgcG9pbnRzPwozLiBXaG8gaGFzIHRoZSBiZXN0IGZpZWxkIGdvYWwgcmF0aW9zIGluIHRoZWlyIGNhcmVlcj8qICAgCjQuIFBsYXllciB3aXRoIHRoZSBiZXN0IG92ZXJhbGwgc3RhdHMgaW4gdGhlaXIgY2FyZWVyPyoKNS4gV2hpY2ggdGVhbSBkaWQgTGVCcm9uIHBsYXkgdGhlIGJlc3Qgb24/KgoKKiB0aGUgcXVlc3Rpb25zIHNlbGVjdGVkIGZvciB0aGUgdmlzdWFsaXphdGlvbgoKCgpcbmV3cGFnZQo8aDM+UXVlc3Rpb24gMSAtIFBsYXllcnMgd2l0aCB0aGUgbW9zdCBwb2ludHMgc2NvcmVkIHNpbmNlIDE5OTYtOTcgc2Vhc29uPzwvaDM+CgpgYGB7cn0KYnlfcGxheWVyIDwtIGdyb3VwX2J5KGdhbWVzLFBMQVlFUikKCm1vc3RfcG9pbnRzX3Njb3JlZCA8LSBzdW1tYXJpc2UoYnlfcGxheWVyLCBwb2ludHMgPSBzdW0oUFRTKSkgJT4lCiAgYXJyYW5nZShkZXNjKHBvaW50cykpICU+JQogIHRvcF9uKDUpCgptb3N0X3BvaW50c19zY29yZWQKYGBgCgpIZXJlIGlzIGEgbGlzdCBvZiB0aGUgdG9wIDUgcGxheWVycyB3aXRoIHRoZSBoaWdoZXN0IHBvaW50cyBzY29yZWQgc2luY2UgMTk5Ni05NyBzZWFzb24uICAKCgpcbmV3cGFnZQo8aDM+UXVlc3Rpb24gMiAtIFBsYXllcnMgd2l0aCB0aGUgbW9zdCBnYW1lcyBvdmVyIDQwIHBvaW50cz88L2gzPgpgYGB7cn0KbW9zdF9nYW1lc19vdmVyXzQwcHRzIDwtIGdyb3VwX2J5KGdhbWVzLFBMQVlFUikgJT4lCiAgZmlsdGVyKFBUUz40MCkgJT4lCiAgY291bnQoUExBWUVSKSAlPiUKICBhcnJhbmdlKGRlc2MobikpICU+JQogIHVuZ3JvdXAoUExBWUVSKSAlPiUKICB0b3Bfbig1KQogIAptb3N0X2dhbWVzX292ZXJfNDBwdHMKCmBgYAoKSGVyZSBpcyBhIGxpc3Qgb2YgdGhlIHRvcCA1IHBsYXllcnMgd2l0aCB0aGUgbW9zdCBnYW1lcyB3aXRoIGF0bGVhc3QgNDAgcG9pbnRzIHNpbmNlIDE5OTYtOTcgc2Vhc29uLiAKCk5vdyB0aGF0IHdlIGhhdmUgYSBmZXcgb2Ygd2hhdCBJIHdvdWxkIGNvbnNpZGVyIHRoZSB0b3AgcGxheWVycyBpbiB0aGUgTkJBLCBhY2NvcmRpbmcgdG8gdG90YWwgcG9pbnRzIHNjb3JlZCBhbmQgbW9zdCBnYW1lcyB3aXRoIGF0bGVhc3QgNDAgcHRzLiBMZXRzIGFuYWx5emUgYSBmZXcgc3RhdHMgZnJvbSB0aGVzZSBpbmRpdmlkdWFscyB0byB0cnkgYW5kIGhlbHAgbmFycm93IGRvd24gYW5kIGRldGVybWluZSB3aG8gaXMgdGhlIGJlc3Qgb2YgdGhlc2UgcGxheWVyIGluIHRoZSBOQkEgc2luY2UgMTk5Ni05NyBzZWFzb24uIAoKClxuZXdwYWdlCjxoMz5RdWVzdGlvbiAzIC0gV2hvIGhhcyB0aGUgYmVzdCBmaWVsZCBnb2FsIHJhdGlvcyBpbiB0aGVpciBjYXJlZXI/IDwvaDM+CgpgYGB7cn0KZmdyIDwtIG11dGF0ZShnYW1lcywgRkdSID0gRkcvRkdBLCBGR1IzUFQgPSBGRzMvRkczQSwgRkdSRlQgPSBGVC9GVEEpICU+JQogIGZpbHRlcihQTEFZRVIgPT0gbW9zdF9nYW1lc19vdmVyXzQwcHRzJFBMQVlFUiB8IFBMQVlFUiA9PSBtb3N0X3BvaW50c19zY29yZWQkUExBWUVSKQoKYmVzdF9mZ3IgPC0gZ3JvdXBfYnkoZmdyLFBMQVlFUikgJT4lCiAgc3VtbWFyaXNlKGF2Z2ZnciA9IG1lYW4oRkdSLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKGRlc2MoYXZnZmdyKSkKCmJlc3RfM3B0ciA8LSBncm91cF9ieShmZ3IsUExBWUVSKSAlPiUKICBzdW1tYXJpc2UoYXZnM3B0ciA9IG1lYW4oRkdSM1BULCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKGRlc2MoYXZnM3B0cikpCgpiZXN0X2Z0ciA8LSBncm91cF9ieShmZ3IsUExBWUVSKSAlPiUKICBzdW1tYXJpc2UoYXZnZnRyID0gbWVhbihGR1JGVCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShkZXNjKGF2Z2Z0cikpCgptZXJnZWRfZmdyIDwtIG1lcmdlKGJlc3RfZmdyLGJlc3RfM3B0cixieS54PSJQTEFZRVIiKSAlPiUKICBtZXJnZShiZXN0X2Z0cixieS54PSJQTEFZRVIiKQptZXJnZWRfZmdyCgpgYGAKClNlZSBWaXogMy4xIGJlbG93LSBUaGlzIHZpc3VhbGl6YXRpb24gaXMgcmVwcmVzZW50ZWQgYnkgYSBiYXIgcGxvdCwgaW4gZGVzY2VuZGluZyBvcmRlci4gVGhlIHJlYXNvbiBmb3IgdGhpcyB0eXBlIG9mIGNoYXJ0IGZvciB0aGlzIGRhdGEgd2FzIHRvIHNob3cgdGhlICJyYW5rIiBvZiB0aGVzZSBlaWdodCAoOCkgcGxheWVycy4gTG9va2luZyBhdCBib3RoIHRoZSB0YWJsZSBhbmQgY2hhcnQgd2lsbCBoZWxwIHVuZGVyc3RhbmQgdGhlIGRhdGEgYmV0dGVyLiBUaGUgb3RoZXIgcGFydCBvZiB0aGlzIHZpc3VhbGl6YXRpb24gdGhhdCBoZWxwcyBpcyB0aGF0IGl0IGlzIHJhbmtlZCBpbiBkZWNzZW5kaW5nIG9yZGVyIHRvIGdpdmUgdGhlIHZpZXdlciBsZXNzIGl0ZW1zIHRvIGxvb2sgYXQgYWxsIGF0IG9uY2UsIGFzIGNvbXBhcmluZyBiYXIgaGVpZ2h0cyBjYW4gYmUgZGVjZWl2aW5nIHNvbWV0aW1lcy4KCiFbVml6IDNfMTogQXZnIEZpZWxkIEdvYWwgUmF0aW8gdG9wIDggcGxheWVyc10oUTNWMS5qcGcpCgpTZWUgVml6IDMuMiBiZWxvdyAtIFRoaXMgdmlzdWFsaXphdGlvbiBpcyByZXByZXNlbnRlZCBieSBhIGxvbGxpcG9wIGNoYXJ0LiBUaGUgcmVhc29uIGZvciB0aGlzIHR5cGUgb2YgY2hhcnQgZm9yIHRoaXMgZGF0YSB3YXMgdG8gc2hvdyB0aGUgc2FtZSBkYXRhIGZyb20gYSBzb21ld2hhdCBkaWZmZXJlbnQgdmlldyAicmFuayIgb2YgdGhlc2UgZWlnaHQgKDgpIHBsYXllcnMuIEhvd2V2ZXIsIHRoZSBkYXRhIGJlaW5nIHJlcHJlc2VudGVkIGhlcmUgZGlmZmVyZW50IGFuZCBpdCBpcyB1c2luZyB0aGUgYXZlcmFnZSAzIHBvaW50IHJhdGlvIChhdmczcHRyKS4gU2ltaWxhcmx5LCB0aGUgY2hhcnQgaXMgaW4gZGVzY2VuZGluZyBvcmRlciB0byBnaXZlIHRoZSB2aWV3ZXIgbGVzcyBpdGVtcyB0byBsb29rIGF0IGFsbCBhdCBvbmNlLgoKIVtWaXogM18yOiBBdmcgM3B0IFJhdGlvIHRvcCA4IHBsYXllcnNdKFEzVjIuanBnKQpTZWUgVml6IDMuMyBiZWxvdyAtIFRoaXMgdmlzdWFsaXphdGlvbiBpcyByZXByZXNlbnRlZCBieSBhIGNpcmNsZSBwbG90IHdpdGggMyBheGVzLCBvbmUgZm9yIGVhY2ggY29sdW1uL2ZlYXR1cmUuIFRoZSByZWFzb24gZm9yIHRoaXMgdHlwZSBvZiBjaGFydCBmb3IgdGhpcyBkYXRhIHdhcyB0byBzaG93IGEgY29tcGFyaXNvbiBiZXR3ZWVuIHBsYXllcnMuIEZvciB0aGUgcHVycG9zZSBhbmQgcHJvb2Ygb2YgY29uY2VwdCwgdXNlZCBpdCB0byByZXByZXNlbnQgb25seSAyIHBsYXllcnMoTGVicm9uIEphbWVzIGFuZCBLb2JlIEJyeWFudCkuIFRoZXJlIHdvdWxkIGhhdmUgYmVlbiAyOCBjb21iaW5hdGlvbnMgZm9yIGFueSAyIHBsYXllcnMsIGFuZCB3b3VsZCBiZXN0IHdvcmsgd2l0aCBhbiBpbnRlcmFjdGl2ZSB2aWV3IG9mIHNvbWUgc29ydCB0byBzZWxlY3QgcGxheWVyIDEgYW5kIHBsYXllciAyIGZvciBjb21wYXJpc29uLiBUaGUgcHVycG9zZSBvZiB0aGlzIHZpc3VhbGl6YXRpb24gd291bGQgYmUgdG8gZWFzaWx5IGJlIGFibGUgdG8gY29tcGFyZSBzdGF0cyBvZiAyIHBsYXllcnMgdmVyc3VzIGhhdmluZyB0byBsb29rIGF0IG11bHRpcGxlIGNoYXJ0cywgYW5kIHBpZWNpbmcgdG9nZXRoZXIgaW5mb3JtYXRpb24uCgohW1ZpeiAzXzM6IENvbXBhcmlzb24gYmV0d2VlbiAyIHBsYXllcnNdKFEzVjMuanBnKQpFeHRyYTogSW4gYWRkaXRpb24sIHRvIHRoZSBjYXJlZXIgc3RhdHMsIHNvbWV0aW1lcyBpdHMgbWVhbmluZ2Z1bCB0byBsb29rIGF0IGEgZmV3IHBlciB5ZWFyIHN0YXRzIGFzIHdlbGwuIEJlbG93IGlzIHRoZSBGaWVsZCBnb2FsIHJhdGlvIHBlciB5ZWFyIGZvciB0aGVzZSBwbGF5ZXJzLCBhbG9uZyB3aXRoIHRoZWlyIHBvaW50cyBwZXIgZ2FtZSBwZXIgeWVhciBzdGF0cy4gVGhpcyB3aWxsIGhlbHAgd2l0aCBtYWtpbmcgc3VyZSB0aGUgcGxheWVycyB0aGF0IGFyZSBzY29yaW5nIGhpZ2hlciBwb2ludHMgYXJlIG5vdCBkb2luZyBzbyBhdCB0aGUgc2FjcmlmaWNlIG9mIHRoZWlyIGZpZWxkIGdvYWwgcGVyY2VudGFnZSwgYW5kIG5vdCBoYXZlIGl0IHBsYXkgaW50byB0aGUgaWRlYSBvZiBtb3JlIHNob3RzIHRha2VuIHRoZSBoaWdoZXIgdGhlIHNjb3JlLiAKCmBgYHtyfQpieV9wbGF5ZXIgPC0gZmlsdGVyKGZncikgJT4lCiAgZ3JvdXBfYnkoUExBWUVSLCB5ZWFyKQoKcHBnX3Blcl95ciA8LSBzdW1tYXJpc2UoYnlfcGxheWVyLCBwb2ludHMgPSBtZWFuKFBUUywgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZSh5ZWFyKQoKcHBnX3Blcl95cgoKIyBwbG90CmdncGxvdChkYXRhID0gcHBnX3Blcl95ciwgYWVzKHg9cHBnX3Blcl95ciR5ZWFyLCB5PXBwZ19wZXJfeXIkcG9pbnRzLCBncm91cCA9IHBwZ19wZXJfeXIkUExBWUVSLCBjb2xvciA9cHBnX3Blcl95ciRQTEFZRVIpKSArIGdlb21fbGluZSgpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQoKYGBgCkFzIHNlZW4gaW4gdGhpcyB2aXN1YWxpemF0aW9uIGFib3ZlLCB0aGUgdG9wIDMgcGxheWVycyBvZiB0aGUgOCB0aGF0IGFyZSBiZWluZyBleGFtaW5lZCBhcmUgSmFtZXMgSGFyZGVuLCBMZWJyb24gSmFtZXMsIGFuZCBLZXZpbiBEdXJhbnQgaW4gdGVybXMgb2YgbW9zdCBwb2ludHMgcGVyIGdhbWUgcGVyIHllYXIuIAoKYGBge3J9CmJ5X3BsYXllciA8LSBmaWx0ZXIoZmdyKSAlPiUKICBncm91cF9ieShQTEFZRVIsIHllYXIpCgpiZXN0X2Zncl9wZXJfeXIgPC0gc3VtbWFyaXNlKGJ5X3BsYXllciwgRkdSX3Blcl95ciA9IG1lYW4oRkdSLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKHllYXIpCgpiZXN0X2Zncl9wZXJfeXIKCiMgcGxvdApnZ3Bsb3QoZGF0YSA9IGJlc3RfZmdyX3Blcl95ciwgYWVzKHg9YmVzdF9mZ3JfcGVyX3lyJHllYXIsIHk9YmVzdF9mZ3JfcGVyX3lyJEZHUl9wZXJfeXIsIGdyb3VwID0gYmVzdF9mZ3JfcGVyX3lyJFBMQVlFUiwgY29sb3IgPWJlc3RfZmdyX3Blcl95ciRQTEFZRVIpKSArIGdlb21fbGluZSgpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQpgYGAKTm93IGluIHRoZSB2aXN1YWxpemFpdG9uIGFib3ZlLCBsZXRzIHRha2UgYSBjbG9zZXIgbG9vayBhdCBKYW1lcyBIYXJkZW4sIExlYnJvbiBKYW1lcywgYW5kIEtldmluIER1cmFudCBpbiB0ZXJtcyBvZiB0aGVpciBmaWVsZCBnb2FsIHBlcmNlbnRhZ2UgcGVyIHllYXIuIE5vdGljZSB0aGF0IEphbWVzIEhhcmRlbiBoYXMgYSBsb3dlciByYXRpbyB0aGFuIGJvdGggTGVicm9uIEphbWVzIGFuZCBLZXZpbiBEdXJhbnQsIGxlYWRpbmcgdXMgdG8gYmVsaWV2ZSBoZSBoYXMgaGlnaGVyIHBvaW50cyBwZXIgZ2FtZSBvbmx5IGJlY2F1c2Ugb2YgaG93IG1hbnkgc2hvdHMgaGUgdG9vayBpbiB0b3RhbC4KClxuZXdwYWdlCjxoMz5RdWVzdGlvbiA0IC0gUGxheWVyIHdpdGggdGhlIGJlc3Qgb3ZlcmFsbCBzdGF0cyBpbiB0aGVpciBjYXJlZXI/IDwvaDM+CmBgYHtyfQpzdGF0cyA8LSBmaWx0ZXIoZmdyKQoKYmVzdF9zdGF0cyA8LSBncm91cF9ieShzdGF0cyxQTEFZRVIpICU+JQogIHN1bW1hcmlzZShhdmdUUkIgPSBtZWFuKFRSQiwgbmEucm0gPSBUUlVFKSwgYXZnQVNUID0gbWVhbihBU1QsIG5hLnJtID0gVFJVRSksIGF2Z1NUTCA9IG1lYW4oU1RMLCBuYS5ybSA9IFRSVUUpLCBhdmdCTEsgPSBtZWFuKEJMSywgbmEucm0gPSBUUlVFKSkgJT4lCiAgbXV0YXRlKHRvdGFsID0gYXZnVFJCK2F2Z0FTVCthdmdTVEwrYXZnQkxLKSAlPiUKICBhcnJhbmdlKGRlc2ModG90YWwpKQoKYmVzdF9zdGF0cwoKYnlfcGxheWVyIDwtIGZpbHRlcihmZ3IpICU+JQogIGdyb3VwX2J5KFBMQVlFUiwgeWVhcikKCmJlc3Rfc3RhdHNfcGVyX3llYXIgPC0gc3VtbWFyaXNlKGJ5X3BsYXllciwgYXZnVFJCID0gbWVhbihUUkIsIG5hLnJtID0gVFJVRSksIGF2Z0FTVCA9IG1lYW4oQVNULCBuYS5ybSA9IFRSVUUpLCBhdmdTVEwgPSBtZWFuKFNUTCwgbmEucm0gPSBUUlVFKSwgYXZnQkxLID0gbWVhbihCTEssIG5hLnJtID0gVFJVRSkpICU+JQogIG11dGF0ZSh0b3RhbCA9IGF2Z1RSQithdmdBU1QrYXZnU1RMK2F2Z0JMSykgJT4lCiAgYXJyYW5nZShkZXNjKHRvdGFsKSkKCmJlc3Rfc3RhdHNfcGVyX3llYXIKCmdncGxvdChkYXRhID0gYmVzdF9zdGF0c19wZXJfeWVhciwgYWVzKHg9YmVzdF9zdGF0c19wZXJfeWVhciR5ZWFyLCB5PWJlc3Rfc3RhdHNfcGVyX3llYXIkdG90YWwsIGdyb3VwID0gYmVzdF9zdGF0c19wZXJfeWVhciRQTEFZRVIsIGNvbG9yID1iZXN0X3N0YXRzX3Blcl95ZWFyJFBMQVlFUikpICsgZ2VvbV9saW5lKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpCgpgYGAKU2VlIFZpeiA0LjEgYWJvdmUtIFRoaXMgdmlzdWFsaXphdGlvbiBpcyByZXByZXNlbnRlZCBieSBhIG11bHRpcGxlIGxpbmUgcGxvdC4gVGhpcyB2aXN1YWxpemF0aW9uIGFsbG93cyB0aGUgdXNlciB0byB2aWV3IDMgdmFyaWFibGVzIGF0IHRoZSBzYW1lIHRpbWUuIFRoZSB2aXN1YWxpemF0aW9uIGlzIGFibGUgdG8gc2hvdyB0aGUgdG90YWwgc3RhdHMoeSBheGlzKSwgdGhlIHllYXIoeCBheGlzKSwgYW5kIHRoZSBwbGF5ZXIgKGNvbG9yZWQgbGluZSkuVGhlIHkgYXhpcyBpcyBjYWxjdWxhdGVkIGJ5IGFkZGluZyB0b2dldGhlciB0aGUgYXZlcmFnZSB0b3RhbCByZWJvdW5kcyBwZXIgZ2FtZSwgYXZlcmFnZSBhc3Npc3QgcGVyIGdhbWUsIGF2ZXJhZ2Ugc3RlYWwgcGVyIGdhbWUsIGFuZCBhdmVyYWdlIGJsb2NrIHBlciBnYW1lIC0gYWxsIGZvciBhIGdpdmVuIHllYXIuIFRoaXMgaXMgYWxzbyBqdXN0IGEgc2ltcGxpZmljYXRpb24gb2YgdGhlIHN0YXRzLCBhcyBpbiB0aGUgcmVhbCBnYW1lIHRoZXJlIGFyZSB3ZWlnaHRzIGFzc29jaWF0ZWQgd2l0aCB0aGVzZSBzdGF0cywgd2hlcmUgcmVib3VuZHMgYW5kIGFzc2lzdHMgYXJlIG1vcmUgaGVhdmlseSB3ZWlnaGVkIHRoYW4gYmxvY2tzIGFuZCBzdGVhbHMuIAoKClNlZSBWaXogNC4yIGJlbG93LSBUaGlzIHZpc3VhbGl6YXRpb24gaXMgcmVwcmVzZW50ZWQgYnkgYSBtdWx0aXBsZSBtYXJrcy4gVGhpcyB2aXN1YWxpemF0aW9uIGFsc28gYWxsb3dzIHRoZSB1c2VyIHRvIHZpZXcgMyB2YXJpYWJsZXMgYXQgdGhlIHNhbWUgdGltZS4gSG93ZXZlciwgaW4gdGhpcyB2aXN1YWxpemF0aW9uIGl0IHNob3dzIHRoZSBzZXBhcmF0ZSBzdGF0cyBwZXIgcGVyc29uIGFuZCBub3QgcGVyIHllYXIuIFRoZSBhdmVyYWdlIHN0YXQgc2NvcmUgaXMgb24gdGhlIHkgYXhpcywgdGhlIHN0YXQgb24gdGhlIHggYXhpcywgYW5kIGVhY2ggcGxheWVyIGlzIHJlcHJlc2VudGVkIGJ5IGEgZGlmZmVyZW50IG1hcmsuVGhlIHkgYXhpcyByZXByZXNlbnQgdGhlIGF2ZXJhZ2UgdG90YWwgcmVib3VuZHMgcGVyIGdhbWUsIGF2ZXJhZ2UgYXNzaXN0IHBlciBnYW1lLCBhdmVyYWdlIHN0ZWFsIHBlciBnYW1lLCBhbmQgYXZlcmFnZSBibG9jayBwZXIgZ2FtZSAtIGluIGEgcGxheWVycyB3aG9sZSBjYXJlZXIuIAoKIVtWaXogNF8yOiBTdGF0cyBEb3QgUGxvdCBvZiB0aGUgdG9wIDggcGxheWVyc10oUTRWMi5qcGcpCgpTZWUgVml6IDQuMyBiZWxvdy0gVGhpcyB2aXN1YWxpemF0aW9uIGlzIHJlcHJlc2VudGVkIGJ5IHN0YWNrZWQgYmFyIGNoYXJ0LiBUaGlzIHZpc3VhbGl6YXRpb24gYWxzbyBhbGxvd3MgdGhlIHVzZXIgdG8gdmlldyBtdWx0aXBsZSB2YXJpYWJsZXMgYXQgb25jZSBhcyB3ZWxsLiBJbiB0aGlzIHZpc3VhbGl6YXRpb24sIHRoZSBzdGFja2VkIGJhciBpcyBhYmxlIHRvIHNob3cgdGhlIGJyZWFrZG93biBvZiB0aGUgdG90YWwgc3RhdC4gVGhlIHRvdGFsIHN0YXQgc2NvcmUgaXMgb24gdGhlIHkgYXhpcywgdGhlIHBsYXllciBpcyBvbiB0aGUgeCBheGlzLCBhbmQgZWFjaCBzdGF0IGlzIHJlcHJlc2VudGVkIGJ5IGEgZGlmZmVyZW50IHNoYWRpbmcvdGV4dHVyZS5UaGVzZSBhcmUgYWxzbyByZXByZXNlbnRpbmcgd2hvZWwgY2FyZWVyIHN0YXRzIHZlcnN1cyB0aGUgeWVhcmx5IHN0YXRzIHNlZW4gaW4gVml6IDRfMS4gCgohW1ZpeiA0XzM6IFN0YWNrZWQgYmFyIGNoYXJ0IG9mIHRoZSB0b3AgOCBwbGF5ZXJzXShRNFYzLmpwZykKClRvIGNvbnRpbnVlIHRoZSBjb252ZXJzYXRpb24gb2Ygd2hvIGlzIGJlc3QsIGluaXRpYWxseSBpbiBWaXogNF8xIGl0IGNhbiBiZSBzZWVuIHRoYXQgRGlyayBOb3dpdHpraSBhbmQgVGltIER1bmNhbiB3ZXJlIG9uIHRvcCwgdW50aWwgTGVicm9uIEphbWVzIHN0YXJ0ZWQgaGlzIGNhcmVlciBpbiAyMDAzLCBhbmQgdGhleSBzdGFydGVkIG9uIHdoYXQgbG9va3MgdG8gYmUgYSBzdGVhZHkgZGVjbGluZSB1bnRpbCByZXRpcmVtZW50LiBMZWJyb24gSmFtZXMgc2luY2Ugc3RhcnRpbmcgaW4gMjAwMywgaGFkIDEyIG91dCBvZiAxOCB5ZWFycyBvZiBiZWluZyBvbiB0aGUgdG9wIG9mIHRoaXMgY2hhcnQsIGFuZCB0aGUgb3RoZXIgNiB5ZWFycyBoZSB3YXMgc2Vjb25kIG9uIHRoaXMgY2hhcnQuIExlYnJvbiBKYW1lcyBoYXMgdGhlIGhpZ2hlc3Qgb3ZlcmFsbCBzdGF0IHRvdGFsIGZyb20gdGhlIDggcGxheWVycyBzZWVuIGluIFZpeiA0XzMuCgpTYWZlIHRvIHNheSB0aGF0IHN0YXRpc3RpY2FsbHksIExlYnJvbiBKYW1lcyBpcyB0aGUgYmVzdCBvdmVyYWxsIHBsYXllciBpbiB0aGUgTkJBIGFjY29yZGluZyB0byB0aGlzIGRhdGFzZXQgdGhhdCBjb3ZlcnMgYWxsIGdhbWVzIGZyb20gMTk5Ni05NyBzZWFzb24gdG8gRGVjZW1iZXIgMzEsIDIwMjAuCgpcbmV3cGFnZQo8aDM+UXVlc3Rpb24gNSAtIFdoaWNoIHRlYW0gZGlkIExlQnJvbiBwbGF5IHRoZSBiZXN0IG9uPyA8L2gzPgpgYGB7cn0KYnlfcGxheWVyIDwtIGZpbHRlcihnYW1lcywgUExBWUVSPT0iTGVCcm9uIEphbWVzIikgJT4lCiAgZ3JvdXBfYnkoVEVBTSx5ZWFyKQoKTGVicm9uX21vc3RfcG9pbnRzX3Blcl95ZWFyIDwtIHN1bW1hcmlzZShieV9wbGF5ZXIsIHBvaW50cyA9IHN1bShQVFMpKSAlPiUKICBhcnJhbmdlKHllYXIpCiAKTGVicm9uX21vc3RfcG9pbnRzX3Blcl95ZWFyCgojIHBsb3QKZ2dwbG90KGRhdGEgPSBMZWJyb25fbW9zdF9wb2ludHNfcGVyX3llYXIsIGFlcyh4PUxlYnJvbl9tb3N0X3BvaW50c19wZXJfeWVhciRURUFNLCB5PUxlYnJvbl9tb3N0X3BvaW50c19wZXJfeWVhciRwb2ludHMsIGdyb3VwID0gTGVicm9uX21vc3RfcG9pbnRzX3Blcl95ZWFyJFRFQU0sIGNvbG9yPUxlYnJvbl9tb3N0X3BvaW50c19wZXJfeWVhciRURUFNKSkgKyBnZW9tX2JveHBsb3QoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkKCmBgYApTZWUgVml6IDUuMSBhYm92ZS0gVGhpcyB2aXN1YWxpemF0aW9uIGlzIHJlcHJlc2VudGVkIGJ5IGEgbXVsdGlwbGUgYm94IHBsb3RzLiBUaGlzIHZpc3VhbGl6YXRpb24gYWxsb3dzIHRoZSBzcHJlYWQgb2Ygc2NvcmVzLiBUaGUgdmlzdWFsaXphdGlvbiBpcyBhYmxlIHRvIHNob3cgdGhlIHRvdGFsIHBvaW50cyBwZXIgeWVhcih5IGF4aXMpLCB0aGUgdGVhbSh4IGF4aXMpLiBUaGlzIGNoYXJ0IHdhcyBjaG9zZW4gdG8gbm90IG9ubHkgc2hvdyB0aGUgTGVicm9uJ3MgYXZlcmFnZSBwb2ludHMgc2NvcmVkIHBlciB5ZWFyLCBidXQgdG8gc2hvdyB0aGUgcmFuZ2Ugb2Ygc2NvcmVzIGhlIGhhcyBzY29yZWQgZm9yIGEgcGFydGljdWxhciB0ZWFtLiBOb3RpY2UgdGhhdCB0aGUgYm94IHBsb3QgZm9yIExha2VycyBpcyBzbWFsbCwgdGhhdCBpcyB1bHRpbWF0ZWx5IGR1ZSB0byB0aGUgbGltaXRlZCBudW1iZXIgb2YgeWVhcnMgaGUgaGFzIGJlZW4gd2l0aCB0aGF0IGZyYW5jaGlzZS4gT24gYXZlcmFnZSwgTGVicm9uIGhhZCB0aGUgbW9zdCBwb2ludHMgc2NvcmVkIHBlciB5ZWFyIG9uIHRoZSBDbGV2ZWxhbmQgQ2F2YWxpZXJzLgoKYGBge3J9CgpMZWJyb25fYXZnX3BvaW50c19wZXJfeWVhciA8LXN1bW1hcmlzZShieV9wbGF5ZXIsIGF2Z01pbnNQbGF5ZWQgPSBtZWFuKE1QLCBuYS5ybT1UUlVFKSwgcG9pbnRzID0gbWVhbihQVFMsIG5hLnJtPVRSVUUpKSAlPiUKICBhcnJhbmdlKHllYXIpCgpMZWJyb25fYXZnX3BvaW50c19wZXJfeWVhcgoKIyBwbG90CmdncGxvdChkYXRhID0gTGVicm9uX2F2Z19wb2ludHNfcGVyX3llYXIsIGFlcyh4PUxlYnJvbl9hdmdfcG9pbnRzX3Blcl95ZWFyJHllYXIsIHk9TGVicm9uX2F2Z19wb2ludHNfcGVyX3llYXIkcG9pbnRzLCBncm91cCA9IExlYnJvbl9hdmdfcG9pbnRzX3Blcl95ZWFyJFRFQU0sIGNvbG9yPUxlYnJvbl9hdmdfcG9pbnRzX3Blcl95ZWFyJFRFQU0pKSArIGdlb21fcG9pbnQoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkKCmBgYAoKU2VlIFZpeiA1LjIgYWJvdmUtIFRoaXMgdmlzdWFsaXphdGlvbiBpcyByZXByZXNlbnRlZCBieSBhIHNjYXR0ZXIgcGxvdC4gVGhpcyB2aXN1YWxpemF0aW9uIGFsbG93cyB0byBzcHJlYWQgb2Ygc2NvcmVzIGFjcm9zcyBtdWx0aXBsZSB5ZWFycy4gVGhlIHZpc3VhbGl6YXRpb24gaXMgYWJsZSB0byBzaG93IHRoZSBhdmVyYWdlIHBvaW50cyBwZXIgZ2FtZSBwZXIgeWVhcih5IGF4aXMpLCB0aGUgeWVhcih4IGF4aXMpLiBUaGlzIGNoYXJ0IHdhcyBjaG9zZW4gdG8gdG8gc2hvdyBob3cgTGVicm9uIHNjb3JlZCBvbiBhdmVyYWdlIGluIGFueSBnaXZlbiB5ZWFyLiBOb3RpY2UgdGhhdCBvbmx5IGhpcyBmaXJzdCAyIHllYXJzIGluIHRoZSBOQkEgZGlkIGhlIGhhdmUgYSAyMi41IGFuZCBsb3dlciBhdmVyYWdlIGdhbWUgc2NvcmUgKHdoaWNoIGlzIHN0aWxsIHZlcnkgaW1wcmVzc2l2ZSkuIEFsc28gdGhhdCBoaXMgdG9wIHNlYXNvbnMgd2VyZSB3aXRoIHRoZSBDbGV2ZWxhbmQgQ2F2YWxpZXJzLiAKCgpgYGB7cn0KTGVicm9uX3RlYW1fc3VtbWFyeSA8LSBmaWx0ZXIoZ2FtZXMsIFBMQVlFUj09IkxlQnJvbiBKYW1lcyIpICU+JQogIGdyb3VwX2J5KFRFQU0pICU+JQogIGNvdW50KCkKCkxlYnJvbl90ZWFtX3N1bW1hcnkKCkxlQnJvbl9Qb2ludHNfU3VtbWFyeSA8LSB1bmdyb3VwKExlYnJvbl9tb3N0X3BvaW50c19wZXJfeWVhcikgJT4lCiAgZ3JvdXBfYnkoVEVBTSkgJT4lCiAgc3VtbWFyaXplKFRvdGFsUHRzID0gc3VtKHBvaW50cykpCgpMZUJyb25fUG9pbnRzX1N1bW1hcnkKICAKYGBgClNlZSBWaXogNS4zIGJlbG93LSBUaGlzIHZpc3VhbGl6YXRpb24gaXMgcmVwcmVzZW50ZWQgYnkgcGllIGNoYXJ0LiBUaGlzIHZpc3VhbGl6YXRpb24gYWxzbyBhbGxvd3MgdGhlIHVzZXIgdG8gdmlldyByZWxhdGl2ZSBzaGFyZSBvZiBnYW1lcyBwbGF5ZWQgYW5kIHRvdGFsIHBvaW50cyBzY29yZWQgd2l0aCBhIGNlcnRhaW4gdGVhbS4gTm90aWNlIGhlcmUgdGhhdCB0aGUgZ2FtZXMgcGxheWVkIHNoYXJlIGFuZCB0aGUgcG9pbnRzIHNjb3JlZCBmb3IgTGVicm9uIGFyZSB2ZXJ5IGhpZ2hseSBjb3JyZWxhdGVkLiB3aGljaCBtZWFucyBoZSBoYXMgYmVlbiByZWFsbHkgY29uc2lzdGVudCBkdXJpbmcgaGlzIHRpbWUgd2l0aCBlYWNoIHRlYW0uIAoKIVtWaXogNV8zOiBMZUJyb24gSmFtZXMgb3ZlcmFsbCBHYW1lIFN1bW1hcnldKFE1VjMuanBnKQoKSW4gY29uY2x1c2lvbiB0byB0aGlzIHF1ZXN0aW9uLCB0aGUgdGVhbSB0aGF0IGhlIHBlcmZvcm1lZCBoaXMgYmVzdCBzbyBmYXIgaXMgd2l0aCB0aGUgQ2xldmVsYW5kIENhdmFsaWVycy4gCg==